<?php

namespace App\Model;

use EasySwoole\FastCache\Cache;
use EasySwoole\ORM\AbstractModel;

/**
 * 缓存基类
 */
class CacheModel extends AbstractModel
{
    protected $primaryKey = 'id';

    // 是否启用缓存
    protected $is_cache = false;


    /**
     * 获取列表缓存
     *
     * @param array $map 查询条件
     * @param bool $is_pri 是否只缓存主键true或false
     * @param bool $pri_key 是否以主键作为键值true或false
     * @return mixed 返回结果
     */
    public function getAll(array $map = [], bool $is_pri = false, bool $pri_key = false)
    {
        $list = $this->getCacheFunc('all', $map, $is_pri, $pri_key);
        return $list;
    }

    /**
     * 重置列表缓存
     *
     * @param array $map 查询条件
     * @param mixed $need_field 需要的字段或者传入true表示只需要主键
     * @param bool $pri_key 是否以主键作为键值true或false
     * @return bool 返回结果true(重置成功)或false(重置失败)
     */
    public function cacheResetAll(array $map = [], $need_field = false, bool $pri_key = false)
    {
        return $this->resetCacheFunc('all', $map, $need_field, $pri_key);
    }

    /**
     * 获取缓存的函数，该函数会先查询缓存，如果缓存不存在，则从数据库中获取并存入缓存中
     *
     * @param string $funcName 方法名
     * @param array|int $id 记录ID
     * @return mixed 返回结果
     */
    public function getCacheFunc(string $funcName, $id)
    {
        $arg_list = func_get_args();
        $cache_key = $this->getCacheKey($funcName, $id);
        $data = $this->getCache($cache_key); // 先看缓存是否已经存在数据
        if (!$data) { // 不存在，则需要查询数据库
            if ($this->tableName) {
                array_shift($arg_list);
            }
            $act = "cache" . ucfirst($funcName);
            $data = call_user_func_array(array($this, $act), $arg_list);
            $this->setCache($cache_key, $data);
        }
        return $data;
    }

    /**
     * 重置缓存，就是根据插入和更新来重新生成缓存
     *
     * @param int $id 记录ID
     * @param array $data 数据源
     * @param bool $is_edit 是否编辑true或false
     * @return mixed 返回结果
     */
    public function cacheReset(int $id, array $data = [], bool $is_edit = false)
    {
        if (!$data) { // 如果没有数据记录，则删除该id的缓存数据
            $this->resetCacheFunc('info', $id);
        }
        $info = [];
        if ($is_edit) { // 如果是更新数据，则先从缓存中获取，如果没有，则从数据库中重新获取，因为更新的数据不是完整的数据记录
            $info = $this->getCacheFunc("info", $id);
        }
        if (is_array($data)) {
            $info = array_merge($info, $data); // 用新的数据覆盖老的数据
        } else {
            $info = $data;
        }
        $cache_key = $this->getCacheKey('info', $id);
        $result = $this->setCache($cache_key, $info); // 如果是更新，则直接覆盖老的缓存
        return $result;
    }

    /**
     * 从数据库中获取需要缓存的数据
     *
     * @param int $id 记录ID
     * @return array 返回结果
     */
    public function cacheInfo(int $id)
    {
        if (!$id) {
            return false;
        }
        $data = $this->get(['id' => $id]);
        return !empty($data) ? $data->toArray() : [];
    }

    /**
     * 删除缓存
     *
     * @param int $id 记录ID
     * @return bool 返回结果
     */
    public function cacheDelete($id)
    {
        $cache_key = $this->getCacheKey("info", $id);
        $result = $this->deleteCache($cache_key);
        return $result;
    }

    /**
     * 设置整表缓存
     *
     * @param array $map 查询条件
     * @param mixed $need_field 需要的字段或者传入true表示只需要主键
     * @param bool $pri_key 是否以主键作为键值true或false
     * @return array 返回结果
     */
    public function cacheAll(array $map = [], $need_field = false, bool $pri_key = false)
    {
        // 格式化查询条件
        if (method_exists($this, 'formatQuery')) {
            $query = $this->formatQuery($this, $map);
        }

        // 是否缓存主键
        if ($need_field) {
            if (is_array($need_field)) {
                // 字段数组
                $query->field($need_field);
            } elseif (is_string($need_field)) {
                // 字段字符串
                $fields = explode(',', $need_field);
                $query->field($fields);
            } else {
                // 默认主键ID
                $query->field($this->primaryKey);
            }
        }

        // 查询数据并转数组
        $result = $query->all()->toArray();
//        $list = [];
//        if ($result) {
//            foreach ($result as $val) {
//                $list[] = $val->toArray();
//            }
//        }
        // 设置主键ID为数组键值
        if ($pri_key) {
            $list = array_column($result, null, $this->primaryKey);
        }

        return $list;
    }


    /**
     * 重置缓存函数
     *
     * @param string $funcName 方法名
     * @param array | string $id 记录ID
     * @return bool 返回结果true或false
     */
    public function resetCacheFunc(string $funcName, string $id = '')
    {
        $cache_key = $this->getCacheKey($funcName, $id);
        $result = $this->deleteCache($cache_key);
        return $result;
    }


    /**
     * 设置缓存
     *
     * @param string $cache_key 缓存KEY
     * @param array $data 缓存数据
     * @param int $ttl 缓存时间(默认永久)
     * @return mixed
     */
    public function setCache($cache_key, $data, $ttl = 0)
    {
        // 事务中可能存在数据库操作，这里需要记录下，当事务回滚时需要移除缓存
        // TODO 这里应该保存在协程上下文中
        if (isset($GLOBALS['trans']) && $GLOBALS['trans'] === true) {
            $GLOBALS['trans_keys'][] = $cache_key;
        }
        // 不设置缓存，直接返回，这是全局缓存开关
        if (!$this->is_cache) {
            return true;
        }
        if (!$data) {
            return Cache::getInstance()->set($cache_key, null);
        }
        $isGzcompress = gzcompress(json_encode($data));
        if ($isGzcompress) {
            return Cache::getInstance()->set($cache_key, $isGzcompress, $ttl);
        }
        return false;
    }

    /**
     * 获取缓存
     *
     * @param string $cache_key 缓存KEY
     * @return mixed 返回结果
     */
    public function getCache($cache_key)
    {
        $data = Cache::getInstance()->get($cache_key);
        if ($data) {
            $data = json_decode(gzuncompress($data), true);
        }
        return $data;
    }

    /**
     * 删除缓存
     *
     * @param string $cache_key 缓存KEY
     * @return bool 返回结果
     */
    public function deleteCache(string $cache_key)
    {
        // 判断缓存KEY是否存在,存在则删除
        if (!empty(Cache::getInstance()->get($cache_key))) {
            return Cache::getInstance()->unset($cache_key);
        }
        return false;
    }

//    /**
//     * 格式化查询条件
//     * @param AbstractModel $model
//     * @param array|string $map 查询条件
//     * @return mixed 返回结果
//     */
//    public function formatQuery(AbstractModel $model, $map = [])
//    {
//        if (is_array($map)) {
//            $map['mark'] = 1;
//        } elseif ($map) {
//            $map .= " AND mark=1 ";
//        } else {
//            $map .= " mark=1 ";
//        }
//        $query = $model->where($map);
//        return $query;
//    }

    /**
     * 获取缓存KEY
     *
     * @return string 返回缓存KEY
     */
    private function getCacheKey()
    {
        $arg_list = func_get_args();
        if ($this->tableName) {
            array_unshift($arg_list, $this->tableName);
        }
        foreach ($arg_list as $key => $val) {
            if (is_array($val)) { // TODO 这里需要改进，查询条件应该作为键的组成部分
                unset($arg_list[$key]);
            }
        }
        $cache_key = implode("_", $arg_list);
        return $cache_key;
    }
}
